package org.fjsei.yewu.bpm;

import com.alibaba.fastjson2.JSON;
import io.camunda.spring.client.exception.BpmnError;
import io.camunda.zeebe.client.api.response.ActivatedJob;
import io.camunda.zeebe.client.api.worker.JobClient;
import io.camunda.zeebe.spring.client.annotation.JobWorker;
import io.camunda.zeebe.spring.client.annotation.Variable;
import io.camunda.zeebe.spring.client.annotation.VariablesAsType;
//import io.camunda.zeebe.spring.client.exception.ZeebeBpmnError;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import md.cm.flow.ApprovalStm;
import md.cm.unit.Unit;
import md.specialEqp.*;
import md.specialEqp.inspect.Agreements;
import md.specialEqp.inspect.Task;
import md.specialEqp.inspect.TaskState_Enum;
import md.specialEqp.type.PipingUnit;
import md.specialEqp.type.PipingUnitRepository;
import md.system.User;
import md.system.UserRepository;
import org.fjsei.yewu.pojo.jsn.EqpDat;
import org.fjsei.yewu.pojo.jsn.IdCrDate;
import org.fjsei.yewu.pojo.sei.TaskDetailAssDat;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.service.core.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.Enumerated;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static graphql.Assert.*;
import static jakarta.persistence.EnumType.STRING;

/**通用请求流程上的工作者代码
* */
@Slf4j
//@ConditionalOnProperty(name = "sei.zeebe", havingValue = "true")
@Component
public class RequestWorker extends Comngrql {
    @Autowired
    private UniversalService universalService;
    private final Agreements agreements;
    private final UserRepository userRepository;
    private final Requests requests;
    private final Equipments equipments;
    private final PipingUnitRepository pipingUnitRepository;
    private final TaskService taskService;
    public RequestWorker(Agreements agreements, UserRepository userRepository, Requests requests, Equipments equipments, PipingUnitRepository pipingUnitRepository, TaskService taskService) {
        this.agreements = agreements;
        this.userRepository = userRepository;
        this.requests = requests;
        this.equipments = equipments;
        this.pipingUnitRepository = pipingUnitRepository;
        this.taskService = taskService;
    }

    //必须是报告流转的 FlowStat?
    private ApprovalStm setApprovalStmStatus(AskForApprProcessVariables variables, RequestSta_Enum status){
        Request entFlow = entityOf(variables.getEnt(), Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        entFlow.setStatus(status);        //当前位于进入审核环节
        requests.save(entFlow);
        return null;
    }
    private ApprovalStm setApprovalStmStatus(String entId,RequestSta_Enum status){
        Request entFlow = entityOf(entId, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        entFlow.setStatus(status);        //当前位于进入审核环节
        requests.save(entFlow);
        return null;
    }
    //手动提交@JobWorker版本; 支持修改流程变量。
//    @Deprecated
////    @JobWorker(type="request_status_show", autoComplete=false)
//    public void onAgreementStatusShow_删除(final JobClient client, final ActivatedJob job, @VariablesAsType AskForApprProcessVariables variables){
//        //ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.CHECK);        //当前位于进入审核环节
//        log.info("协议 申请单, 结果：{}", variables);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
//        variables.setPromoter("herzhang");
//        client.newCompleteCommand(job.getKey()).variables(variables).send()
//                .exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
//    }
    /**局部使用的结构类：设备状态变更
     * */
    @Getter
    @Setter
    @NoArgsConstructor
    public class EqpOpenCloseDat {
        private List<EqpDat>  devs;       //可json提取恢复的不是全面的Entity数据？？
        private String  理由;
        //reg:注册或注销流程 在监察平台做的受理，@检验平台#不涉及这个关口的核准申请。
        @Enumerated(STRING)
        private UseState_Enum   ust;        //所有设备统一变更为一个状态的？  //json存储是字符串"ust": "USE",
    }

    /**设备状态变更;
     * 特别注意，可能调试或某个错误导致本段代码重复运行的情况！
     * 【幂等性】如何确保。 外部第三方请求多次发起的话，应答可能多个的每次都会不同啊.?
     * */
//    @JobWorker(type="do_eqpOpenclose")
//    @Transactional
    public String  do_eqpOpenclose(Request entFlow) {
        String authorDat=entFlow.getDat();
        //耦合代码：提取非结构化的申请单业务信息。
        EqpOpenCloseDat dat = JSON.parseObject(authorDat, EqpOpenCloseDat.class);
        List<Eqp> eqps=new ArrayList<Eqp>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
        LocalDate today=LocalDate.now();       //今天的 ：注意服务器时间同步。时间差不能太大，NTP 有毛病的。
        dat.getDevs().forEach(item -> {
            Eqp eqp=entityOf(item.getId(),Eqp.class);       //对接多个第三方的监察平台？默认福建省的；
            assertNotNull(eqp, () -> "没该设备");
            assertNotNull(item.getCrDate(), () -> "crDate空");
            assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
            assertTrue(item.getCrDate().isBefore(today), () -> "crDate不牢靠");
            eqps.add(eqp);
        });
        AtomicReference<String> subp = new AtomicReference<>("");
        eqps.forEach(eqp -> {
            if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                eqp.setUst(dat.getUst());
                log.info("设备状态变更申请单,本平台直接变更：cod={}", eqp.getCod());
            }else{
                //是否需要启动子流程：外部监察平台处理或等待结果地独立流程。
                //只能仰仗监察接口驱动本功能了。本地设备台账修改没有长期效果的，必须源头变更。除非这个设备是本平台自己才有的设备。
                //本地上传的见证材料？ 监察的对方审核人员也需要能够读取的到啊：URL方式，对方人员在我方平台也要User登录的还是/free/通道的，或者根本只有文件系统的访问path(后端代理访问)。
                log.info("设备状态变更申请单转交给监察平台去做oid={}, cert={}", eqp.getOid(),eqp.getCert());     //为每个设备给监察平台发出具体状态变革申请。可能分解成多个的监察处理单子啊？？
                subp.set("ledgerSync");
            }
        });
        equipments.saveAll(eqps);      //可能本平台就能变更掉了: 【注意】少了这句不能确保肯定保存
//        requests.save(entFlow);
//        Map<String, Object>  variablem=new HashMap<>();
//        log.info("设备状态变更申请单, 审批处置结束：{}", ent);     //【奇怪】为何这个节点等待5分多钟时间啊，毛病
        return subp.get();       //不需要等待监察平台反馈的
    }


//    @JobWorker(type = "foo")
//    public void handleJobFoo(final JobClient client, final ActivatedJob job) {
//        client.newCompleteCommand(job.getKey())
//                .send()
//                .exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
//           }


/*       #Return a Map, String, InputStream, or Object, which then will be added to the process variables
    @JobWorker(type = "foo")
    public Map<String, Object> handleJobFoo(final ActivatedJob job) {
      if (successful) {
             // some data is returned to be stored as process variable
        return variablesMap;
      } else {
       throw new BpmnError("DOESNT_WORK", "This does not work because...");
      }
    }
    @JobWorker
    public ProcessVariables foo(@VariablesAsType ProcessVariables variables, @CustomHeaders Map<String, String> headers){
      return variables;
    }
* */

    //真的没有代码来针对节点type=“”处理的， zeebe走不动也不报警的，被滞留无法终结的流程实例。
    @JobWorker(type="request_req_cancel", autoComplete=true)
    public void handleReportApprove(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
        //ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
//        try {
        ApprovalStm stm=setApprovalStmStatus(variables,RequestSta_Enum.CANCEL);
        log.info("申请单撤销中！ent：{}", variables.getEnt());
//            universalService.goUri(variables.getUri());
//        } catch (DuplicateReportException ex) {
//            throw new BpmnError("duplicateMessage", "Could not post report, it is a duplicate.");
//        }
    }

    @JobWorker(type="request_status_calcfee", autoComplete=true)
    public void handleApprove1(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
//        ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
        log.info("去任务和敲定收费！ent={}", variables.getEnt());
    }
    @JobWorker(type="request_status_feefinish", autoComplete=true)
    public void handleApprove2(@VariablesAsType AskForApprProcessVariables variables) throws Exception {
//        ApprovalStm stm=setApprovalStmStatus(variables,Procedure_Enum.APPR);
        log.info("收费明确！ent={}", variables.getEnt());
    }

    /**统一申请处理结束节点：最后审批完成，并且后端申请单对应的业务代码也执行结束了,
     * 除了监察反馈没完成
     * 这里的输入days很可能是前面两个节点遗留的，所以尽量要求前面一个节点再一次配置补充{且该节点BPMN必须加个days输出Mapping才能真的修改全局变量}。
     * 【默认的】没有加入事务，数据库修改后可能无法保证整个@JobWorker()注解的全部所有代码都正常结束的： 若异常的就会不一致有问题！数据库有保存了可实际状态不一致！。
     * @Transactional 注解必须加上
     * */
//    @JobWorker(type="request_business_finish")
//    @Transactional
//    public void request_business_finish(final JobClient client, final ActivatedJob job,@Variable Short days,
//                               @Variable String ent) throws Exception {
//        //无需等待监察平台的：直接设置END
//        RequestSta_Enum status =(null!=days && days>0)? RequestSta_Enum.FIN : RequestSta_Enum.END;
//        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
//        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
//        entFlow.setStatus(status);        //当前位于进入审核环节
//        entFlow.setMdays(days);     //@特殊：需要存档超期天数，days天数过后，假如监察反馈机制没有起作用的该如何，自动把FIN改成END;
//        requests.save(entFlow);
////        ApprovalStm stm=setApprovalStmStatus(ent,status);
//        log.info("通用申请单审批后处理完成ent={}", ent);      //有些需要等候后续监察平台反馈，才算本申请单实体生命周期结束
//    }

    /**最后一个审批人审核结论跳转：
     *【局限】注入参数不能同时出现@VariablesAsType AskForApprProcessVariables ,@Variable String 混合后@VariablesAsType就没数据！
     * @Variable String ent
     * 注意Worker不同用的流程bpmn文件拷贝type="agreement_entity_status_set"，同名字代码执行随机跳转的，不一定这个代码内执行。所以要确保type在流程引擎zeebe集群范围的唯一性定义避免争夺和差错。
     * zeebe流程图上面Input Mapping实际在该节点运行前准备数据好的字段。
     * @Transactional 必须加： 就算.save()也不能确保真的爆出到数据库。
     * 【特别注意】这个代码涉及数据库事务，很可能需要重新尝试执行的(而CRDB数据库更需要有重试机会)，因此不一定能够立刻都能见到成功运行结束的。
     * 悲观锁失败？Worker rearend failed to handle job with key  of type , sending fail command to broker org.springframework.orm.ObjectOptimisticLockingFailureException:手动改数据遇见？
     * @JobWorker 是否必须放在@Transactional后面的：CRDB事务很可能重试，导致问题大：@Transactional里面掺杂流程触发代码，导致流程引擎实际上早就执行了，可是数据库却还会重试执行，流程代码重复执行啊,流程图已经往前走了吧？！
     * */
    @Transactional
    @JobWorker(type="request_last_appr_status", autoComplete=true)
    public void request_last_appr_status(@Variable String ent,@Variable String approved,@Variable String manager,@Variable String data) throws Exception {
        //实体模型字段  pk 和zeebe状态可能不能同步， 网页缓冲影响？？
        RequestSta_Enum stato="B".equals(approved)? (StringUtils.hasText(manager)? RequestSta_Enum.SUBMIT : RequestSta_Enum.INIT)
                 : "N".equals(approved)? RequestSta_Enum.END : RequestSta_Enum.CHECK;
        //采用BPMN流程图的注入变量？？   if(StringUtils.hasText(entStatus))  stato=RequestSta_Enum.valueOf(entStatus);
//        ApprovalStm stm=setApprovalStmStatus(ent,stato);
        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        entFlow.setAudat(data);
        entFlow.setStatus(stato);        //当前位于进入审核环节
        if("N".equals(approved))
            entFlow.setResult("审批通不过");
        requests.save(entFlow);
        log.info("更新状态：ent={} status={} audat={}",ent,stato, entFlow.getAudat());
    }
    /**局部使用的结构类：设备的单位变更
     * */
    @Getter
    @Setter
    @NoArgsConstructor
    public static class DeviceUnitsDat{
        private List<EqpDat>  devs;
//        private String  理由;   在后端没必要提取的省略掉
        private String   unitZd;        //所有设备统一变更为相同一个配置的单位
        //发送给第三方应用程序接口的有可能需要附加字段:String addon?:来关联反馈的标签定位？ json:非结构化类比IdCrDate做法。
        private IdCrDate  servu;       //通常的非结构化json关联对象存储；恢复验证关联约束。
    }
    /**设备的单位变更;
     * @return true:表示不需要等待监察平台反馈的，可以立刻终结申请单生命周期了。@return false:需要等待预设天数观察看等待监察平台反馈？
     * */
    public String  do_deviceUnits(Request entFlow) {
        String authorDat=entFlow.getDat();
        //耦合代码：提取非结构化的申请单业务信息。
        DeviceUnitsDat dat = JSON.parseObject(authorDat, DeviceUnitsDat.class);
        assertNotNull(dat.getServu(), () -> "没选单位");
        //旧版本Unit unit=fromInputUnitGlobalID(dat.getServu().getId());
        Unit unit= entityOf(dat.getServu().getId(), Unit.class);
        assertNotNull(unit, () -> "没该单位");
        assertTrue(Objects.equals(unit.getCrDate(), dat.getServu().getCrDate()), () -> "crDate不一致");

        List<Eqp> eqps=new ArrayList<Eqp>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
        LocalDate today=LocalDate.now();       //今天的 ：注意服务器时间同步。时间差不能太大，NTP 有毛病的。
        dat.getDevs().forEach(item -> {
            Eqp eqp=entityOf(item.getId(),Eqp.class);       //对接多个第三方的监察平台？默认福建省的；
            assertNotNull(eqp, () -> "没该设备");
            assertNotNull(item.getCrDate(), () -> "crDate空");
            assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
            eqps.add(eqp);
        });
        AtomicReference<String> subp = new AtomicReference<>("");
        eqps.forEach(eqp -> {
            if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                switch (dat.unitZd) {
                    case "useu" -> eqp.setUseu(unit);
                    case "owner" -> eqp.setOwner(unit);
                    case "mtu" -> eqp.setMtu(unit);
                    default -> assertTrue(true, () -> "非法unitZd");
                }
                log.info("设备单位变更申请单,本平台直接变更：cod={}", eqp.getCod());
            }else{
                //需要监察平台来主导变更的，发送给对方平台，离线等待监察的反馈。
                log.info("设备单位变更申请单转交给监察平台去做oid={}, cert={}", eqp.getOid(),eqp.getCert());
                subp.set("ledgerSync");
            }
            //TODO: 只能仰仗监察接口驱动本功能了。本地设备台账修改没有长期效果的，必须源头变更。除非这个设备是本平台自己才有的设备。
            //单位指代还必须和监察平台的是同一个公司：关键字组合（no,..name,）
            //为每个设备给监察平台发出具体状态变革申请。可能分解成多个的监察处理单子啊？？
        });
        equipments.saveAll(eqps);
        return subp.get();
    }

    /**申请单后端业务执行 总入口：
     * 【幂等性】如何确保。
     * 前端操作不如后端方便安全,目前只考虑后端执行业务操作。@前端直接做业务的模式只会授权交互确认更麻烦(监察平台对接会简单?#密码用户login)：还是算了，都走后端代理申请单任务。
     * */
    @Transactional
    @JobWorker(type="request_Business_do")
    public Object  request_Business_do(final JobClient client, final ActivatedJob job,@Variable Short days,
                                   @Variable String ent) {
        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        Map<String, Object>  variablem=new HashMap<>();
        boolean allDone = false;        //若需采用异步回调来确保业务目的的达成与否的，就设置allDone=false。但是请确保有独立模块或机制执行回调。
        switch (entFlow.getMod()) {
            case "deviceUnits" ->{
                String subp= do_deviceUnits(entFlow);
                if(StringUtils.hasText(subp)) {
                    variablem.put("call", subp);        //BPMN文件必须填映射输出本变量,代表开启子流程
                }else allDone=true;
            }
            case "eqpOpenclose" ->{
                String subp= do_eqpOpenclose(entFlow);
                if(StringUtils.hasText(subp)) {
                    variablem.put("call", subp);
                }else allDone=true;
            }
            case "technicalField" -> {
                String subp= do_technicalField(entFlow);
                if(StringUtils.hasText(subp)) {
                    variablem.put("call", subp);
                }else allDone=true;
            }
            case "revisionPermit" -> allDone=true;      //报告修订真正执行要另外途径：额外申请去做或手动的。
            case "revisionExecution" -> allDone=true;
            case "stopInspec" -> allDone=do_stopInspec(entFlow);
            case "taskDelay" -> allDone=do_taskDelay(entFlow);
            case "cancelTask" -> allDone=do_cancelTask(entFlow);
            default -> assertShouldNeverHappen("后端不认识该申请单mod:"+entFlow.getMod());
        }
//        log.info("设备状态变更申请单, 审批处置结束：{}", ent);
        if(allDone){        //不需要等待监察平台反馈的
            days=0;
        }
        variablem.put("days", days);
        //只考虑后端执行业务操作 && 无需等待监察平台的：直接设置END
        RequestSta_Enum status =(null!=days && days>0)? RequestSta_Enum.FIN : RequestSta_Enum.END;
        entFlow.setStatus(status);        //当前位于进入审核环节
        entFlow.setMdays(days);     //@特殊：需要存档超期天数，days天数过后，假如监察反馈机制没有起作用的该如何，自动把FIN改成END;
        requests.save(entFlow);
        log.info("通用申请单审批后处理完成ent={} audat:{}", ent, entFlow.getAudat());      //有些需要等候后续监察平台反馈，才算本申请单实体生命周期结束
        return variablem;
    }
    /**第一关直接领导审批人审核结论跳转：
     * */
    @JobWorker(type="request_director_appr_status", autoComplete=true)
    @Transactional
    public void request_director_appr_status(@Variable String ent,@Variable String approved,@Variable String manager,@Variable String data) throws Exception {
        RequestSta_Enum stato="B".equals(approved)? (StringUtils.hasText(manager)? RequestSta_Enum.SUBMIT : RequestSta_Enum.INIT)
                : "N".equals(approved)? RequestSta_Enum.END : RequestSta_Enum.SUBMIT;
        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        entFlow.setDrdat(data);
        if(StringUtils.hasText(manager)) {
            User nextMan = userRepository.findByUsername(manager);
            assertNotNull(nextMan, () -> "zeebe用户在不在本平台中" + manager);
            entFlow.setManager(nextMan);
        }
        entFlow.setStatus(stato);        //当前位于进入审核环节
        if("N".equals(approved))
            entFlow.setResult("审批通不过");
        requests.save(entFlow);
        log.info("更新状态：ent={} status={}",ent,stato);
    }
    /**第二关主管部门领导审批人审核结论跳转：
     * director若已经是level=1的肯定不会为空吧, (3==level)单关审批；(2==level)有2关审核；但是(1==level)有3关审批。
     * ,@Variable Integer level
     * ”B“回退根本不需要设置角色人，BPMN按预定义人物流转给某个人。BPMN根据有没有director来决定2关还是3关审批的，决定了下一步Request.status变更。
     * 把BPMN流程上的代码尽量挪到@JobWorker里面来了。
     * */
    @JobWorker(type="request_manager_appr_status", autoComplete=true)
    @Transactional
    public void request_manager_appr_status(@Variable String ent,@Variable String approved,@Variable String auditor,@Variable String data,
                                            @Variable String director) throws Exception {
        Request entFlow = entityOf(ent, Request.class);   //或者其他的申请单实体 都 instance of FlowStat
        if(null==entFlow)   throw new BpmnError("notFind", "没找到流转实体");
        RequestSta_Enum stato="B".equals(approved)? (StringUtils.hasText(director)? RequestSta_Enum.SUBMIT : RequestSta_Enum.INIT)
                : "N".equals(approved)? RequestSta_Enum.END : RequestSta_Enum.SUBMIT;
        entFlow.setMndat(data);			//每一个审批层级关每个人自己的数据 主管部门审核
        if(StringUtils.hasText(auditor)) {
            User nextMan = userRepository.findByUsername(auditor);
            assertNotNull(nextMan, () -> "zeebe用户在不在本平台中" + auditor);
            //数据库的变更操作不可以直接放在流转接口函数一起处理，#只能放在独立的@JobWorker来做CRDB更新！
            entFlow.setAuditor(nextMan);
        }
        entFlow.setStatus(stato);        //当前位于进入审核环节
        if("N".equals(approved))
            entFlow.setResult("审批通不过");
        requests.save(entFlow);
        log.info("更新状态：ent={} status={}",ent,stato);
    }
    /**跟随流程开启：从ActivatedJob获取当前流程实例保存到数据库做关联。
     * level没必要提供了：直接依靠人员角色变量：后退的也可能会修改这3个角色人员。创建流程实例时刻设置其中一个角色人：就直接决定了到底启动是1关2关还是3关的审批：前端注意提前判定level，否则摇摆【问题】只能取消旧的实例。
     * 注意前面一关的request_author_modify流程节点：后退到了申请人修改环节的；允许：这一关可以依照level+who来变更审批的关卡数，可重置3个角色人员：可以设定一个清空其它；当然也可以全不变动的。
     * 没加@Transactional 报错 could not initialize proxy  - no Session
     * */
    @JobWorker(type="request_begin_redo_status", autoComplete=true)
    @Transactional
    public Object request_begin_redo_status(final JobClient client, final ActivatedJob job, @VariablesAsType RequestProcessVariables variables) {
        long prId= job.getProcessInstanceKey();
        log.info("开动或再次申请单, prId：{} {}", prId,variables.getTopic());
        Request request= entityOf(variables.getEnt(), Request.class);
        if(null==request)   throw new BpmnError("notFind", "没找到流转实体");
        request.setPrId(prId);
        request.setStatus(RequestSta_Enum.SUBMIT);	  	//假如回退到了编制阶段，再修改申请单:退回==》INIT?
        //【特别】可能要求变换关卡数。导致3个角色被清理了？ 流转给了谁：也需要保存到数据库关联对象。有可能被level变更导致全局变量被重置部分清空了，也可能保留旧的人员。
        //关联Request:有些字段 要重新初始化：
        String usrname=variables.getAuditor();
        boolean changed=false;          //最后审批人 人员配置变动了？
        if(StringUtils.hasText(usrname)) {
            changed= null == request.getAuditor() || !usrname.equals(request.getAuditor().getUsername());
        }else{
            changed= null != request.getAuditor();
        }
        if(changed){
            if(StringUtils.hasText(usrname)) {
                User nextMan = userRepository.findByUsername(usrname);
                Assert.isTrue(nextMan != null, "zeebe用户在不在本平台中:" + usrname);
                request.setAuditor(nextMan);
            }
            else  request.setAuditor(null);
            request.setAudat(null);
        }
        //第二关的看看主管部门领导
        usrname=variables.getManager();
        if(StringUtils.hasText(usrname)) {
            changed= null == request.getManager() || !usrname.equals(request.getManager().getUsername());
        }else{
            changed= null != request.getManager();
        }
        if(changed){
            if(StringUtils.hasText(usrname)) {
                User nextMan=userRepository.findByUsername(usrname);
                Assert.isTrue(nextMan != null,"zeebe用户在不在本平台中:"+usrname);
                request.setManager(nextMan);
            }
            else  request.setManager(null);
            request.setMndat(null);
        }
        //直接领导审核人:
        usrname=variables.getDirector();
        if(StringUtils.hasText(usrname)) {
            changed= null == request.getDirector() || !usrname.equals(request.getDirector().getUsername());
        }else{
            changed= null != request.getDirector();
        }
        if(changed){
            if(StringUtils.hasText(usrname)) {
                User nextMan = userRepository.findByUsername(usrname);
                Assert.isTrue(nextMan != null, "zeebe用户在不在本平台中:" + usrname);
                request.setDirector(nextMan);
            }
            else  request.setDirector(null);
            request.setDrdat(null);
        }

        requests.save(request);
        return variables;
    }
    /**申请单处理完成
     * */
    @JobWorker(type="request_finish_status", autoComplete=true)
    @Transactional
    public void request_finish_status(@Variable String ent) throws Exception {
        Request request = entityOf(ent, Request.class);
        if(null==request)   throw new BpmnError("notFind", "没找到流转实体");
        request.setResult("审批通过执行完");
        requests.save(request);
        log.info("更新状态：ent={}",ent);
    }

    /**暂停监检申请; 任务暂停 延期到时间，前端中止监检通知书盖章 Request.END
     * */
    public boolean  do_stopInspec(Request entFlow) {
        String authorDat=entFlow.getDat();
        TaskDetailAssDat dat = JSON.parseObject(authorDat, TaskDetailAssDat.class);
        Task task=entityOf(dat.getTaskId(),Task.class);
        assertNotNull(task, () -> "没Task");
        if(TaskState_Enum.PERSON.equals(task.getStatus()) || TaskState_Enum.DISP.equals(task.getStatus()) ) {
            task.setStatus(TaskState_Enum.HANGUP);      //任务状态挂起的，也可能需要超期处理？
            task.setOrigd(task.getDate());           //原本的约定 任务日期
            task.setDate(LocalDate.now().plusDays(365*5));   //最长5年中止，5年后重复申请延期？
        }
        else
            assertTrue(TaskState_Enum.HANGUP.equals(task.getStatus()), () -> "任务状态不适合");

        if("线上告知".equals(dat.getSend())) {         //前端判定手机号码合法性。
            assertTrue(StringUtils.hasText(dat.getPhone()), () -> "手机错");
            //TODO：发送通知书链接消息 给客户手机去。 #需要打印通知书并快递的另外请求业务做？
            //每一次流转操作之后就把申请单数据Relay.Invalidate()掉？
            String mess="请点击链接 http://192.168.171.3:3765/free/stopInspec/"+entFlow.getId(null)+" ，查看中止监检通知书";
            log.info("发给{},消息:{}", dat.getPhone(),mess);
        }
        return true;
    }
    /**延期检验任务；需要解析前端的json{}
     * */
    public boolean  do_taskDelay(Request entFlow) {
        String authorDat=entFlow.getDat();
        TaskDetailAssDat dat = JSON.parseObject(authorDat, TaskDetailAssDat.class);
        Task task=entityOf(dat.getTaskId(),Task.class);
        assertNotNull(task, () -> "没Task");
        if(!TaskState_Enum.DONE.equals(task.getStatus()) && !TaskState_Enum.CANCEL.equals(task.getStatus()) ) {
            task.setOrigd(task.getDate());           //原本的约定 任务日期
            task.setDate(dat.get到期日());       //延期 到期日
        }
        else
            assertShouldNeverHappen("任务状态不适合");
        return true;
    }
    /**检验任务 的取消。
     * */
    public boolean  do_cancelTask(Request entFlow) {
        String authorDat=entFlow.getDat();
        TaskDetailAssDat dat = JSON.parseObject(authorDat, TaskDetailAssDat.class);
        String res=taskService.cancellationTask(dat.getTaskId());
        if(StringUtils.hasText(res)){
            //res非空 不一定就没有成功清理任务。
            entFlow.setResult("取消任务应答:["+res+"]");
            return true;
        }
        return true;
    }
    /**局部使用的结构类：关键技术参数变更; 实际上多数的监察才能修改的参数字段并不会使用本途径做变更申请的，更多是首检初始化参数，大修施工告知时刻监察就做主动参数变更的。所以本申请单实际数量不会太多。
     * */
    @Getter
    @Setter
    @NoArgsConstructor
    public class EqpTechnicalFieldDat {
        //是否针对管道单元进行的关键技术参数变更？ 默认=false;
        private Boolean  pipe;   //若是管道单元，那么devs就应当只能唯一一个Eqp:Pipeline，不要混合多个管道装置;
        private List<EqpDat>  devs;       //可json提取恢复的不是全面的Entity数据？？
        //挑选管道单元的情况！ 不是正常设备， 设备->多个管道单元：管道单元底下的几个参数的。
   //todo: 管道单元    private List<EqpDat>  devs; pipingUnits; 【前端非结构化输入的】

        private String  理由;
        private String  eqpType;        //若是管道单元，eqpType字段就无效的；
        //reg:注册或注销流程 在监察平台做的受理，@检验平台#不涉及这个关口的核准申请。 对应前端的export const 关键参数typec={;
        //字段名字： Eqp.<>::实体模型表的属性名字。 #注意：这个配置仅仅挑选参数名，并没有配置每个参数的最新取值。【哪些字段】需要推送的：由业务高层层次定夺,不一定在底层来限定死的。
        private List<String>  paramx;       //所有的设备：统一将会变更这些字段的！一起推送给监察平台。【注意】最好是同一个监察平台，多个平台分叉的麻烦？！
    }
    /**设备关键技术参数的本地变更后需要推送给监察平台的情况 ，监察才能修改的字段的修改 申请，推送;
     * 本地变更后，推送已经变更的信息说明，把说明扔给维护模块去自动提交给监察平台；allDone=false代表需要另外模块来配合指出目的真达成否[异步的+回调机制]。
     * 首先修改本地平台的目标参数，再来申请单当中声明刚刚被您修改的并且需要推送给监察平台的那些技术参数字段，且若这些字段不推送监察将会导致下一次同步监察台账后本地平台的这些参数变更丢失。
     * @return 子流程名称: 分解功能给子流程，可能不在同一个后端服务器执行的。"台账同步子流程"实际放在维护辅助后端代码中实现，不在本后端做。
     * */
    public String  do_technicalField(Request entFlow) {
        String authorDat=entFlow.getDat();
        //实际前端数据模型是{"allmp": [], "devs": [], "eqpType": "4", "oldpars": [], "paramx": []}，而且最终执行过程环节并不在本工程
        EqpTechnicalFieldDat dat = JSON.parseObject(authorDat, EqpTechnicalFieldDat.class);
        LocalDate today=LocalDate.now();       //今天的 ：注意服务器时间同步。时间差不能太大，NTP 有毛病的。
        AtomicReference<String> subp = new AtomicReference<>("");
        if(dat.getPipe()){
            List<PipingUnit> eqps=new ArrayList<PipingUnit>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
            dat.getDevs().forEach(item -> {
                PipingUnit eqp=entityOf(item.getId(),PipingUnit.class);       //对接多个第三方的监察平台？默认福建省的；
                assertNotNull(eqp, () -> "没该设备");
                assertNotNull(item.getCrDate(), () -> "crDate空");
                assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
                assertTrue(item.getCrDate().isBefore(today), () -> "crDate不牢靠");
                eqps.add(eqp);
            });
            eqps.forEach(eqp -> {
                if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                    log.info("管道单元关键技术参数变更申请单,仅需本平台变更的;在申请单前面人工已改好：code={}", eqp.getCode());
                }else{
                    log.info("管道单元关键技术参数变更申请单转交给监察平台去做rno={}", eqp.getRno());
                    subp.set("ledgerSync");
                }
            });
            pipingUnitRepository.saveAll(eqps);      //可能本平台就能变更掉了: 【注意】少了这句不能确保肯定保存
        }
        else {     //若不是管道单元技术参数变更的：
            List<Eqp> eqps=new ArrayList<Eqp>();        //校验json恢复设备crDate+uuID;避免指向错误设备。
            dat.getDevs().forEach(item -> {
                Eqp eqp=entityOf(item.getId(),Eqp.class);       //对接多个第三方的监察平台？默认福建省的；
                assertNotNull(eqp, () -> "没该设备");
                assertNotNull(item.getCrDate(), () -> "crDate空");
                assertTrue(item.getCrDate().equals(eqp.getCrDate()), () -> "crDate不一致");
                assertTrue(item.getCrDate().isBefore(today), () -> "crDate不牢靠");
                eqps.add(eqp);
            });
            eqps.forEach(eqp -> {
                if(RegState_Enum.TODOREG==eqp.getReg() || RegState_Enum.CANCEL==eqp.getReg()){
                    log.info("设备关键技术参数变更申请单,仅需本平台变更的;在申请单前面人工已改好：cod={}", eqp.getCod());
                }else{
                    log.info("设备关键技术参数变更申请单转交给监察平台去做oid={}, cert={}", eqp.getOid(),eqp.getCert());
                    subp.set("ledgerSync");
                }
            });
            equipments.saveAll(eqps);      //可能本平台就能变更掉了: 【注意】少了这句不能确保肯定保存
        }
        return subp.get();       //不需要等待监察平台反馈的： 这个和维护辅助工程的同名函数功能目标不同，这里没有实际给监察。
    }
}



//问题：Job worker 工作者代码必须是幂等的。
//直接用zeebe-client.Api太不友好, 没法直接的对UserTask读取当前流程实例  https://github.com/camunda-community-hub/spring-zeebe
